package mock

import (
	"errors"
	"sort"
	"strconv"
	"strings"
)

const StartIndexValue = 0
const Split = "#"

//GlobalStateStore to mock global state store
//support history
// use  `map<key#histotry_index,value>` and `map<key,histotry_index>` to support history search
type GlobalStateStore struct {
	stateMap map[string]string //to global store
	keyIndex map[string]int    //to store key curr index
}

var _ sort.Interface = (KeyList)(nil)

type KeyList []string

func (k KeyList) Len() int {
	return len(k)
}

func (k KeyList) Less(i, j int) bool {
	return k[i] < k[j]
}

func (k KeyList) Swap(i, j int) {
	k[i], k[j] = k[j], k[i]
}

// NewGlobalStateStore new a GlobalStateStore,
// if initValue is nil, return a empty store
// else init with the initvalue
func NewGlobalStateStore(initValue map[string]string) (*GlobalStateStore, error) {
	result := GlobalStateStore{
		stateMap: map[string]string{},
		keyIndex: map[string]int{},
	}

	for keyWithIndex, value := range initValue {
		key, index, err := parseKeyAndIndex(keyWithIndex)
		if err != nil {
			return nil, err
		}
		result.stateMap[keyWithIndex] = value

		//keyIndex
		oldIndex, ok := result.keyIndex[key]
		if !ok {
			result.keyIndex[key] = index
		} else if index > oldIndex {
			result.keyIndex[key] = index
		}
	}

	return &result, nil
}

//Put put state data to store
func (gss *GlobalStateStore) Put(key, value string) {
	newIndex := StartIndexValue
	if oldIndex, ok := gss.keyIndex[key]; ok {
		newIndex = oldIndex + 1
	}
	gss.keyIndex[key] = newIndex
	gss.stateMap[addIndexToKey(key, newIndex)] = value
}

//Get get state data from store
func (gss *GlobalStateStore) Get(key string) (string, error) {
	if oldIndex, ok := gss.keyIndex[key]; ok {
		return gss.stateMap[addIndexToKey(key, oldIndex)], nil
	}
	return "", nil
}

//GetAllHistoryValue return all history value from old to new(FIFO)
func (gss *GlobalStateStore) GetAllHistoryValue(key string) ([]string, error) {
	maxIndex, ok := gss.keyIndex[key]
	if !ok {
		return nil, errors.New("[GetAllHistoryValue] key not found")
	}
	result := make([]string, maxIndex+1, maxIndex+1)
	for index := StartIndexValue; index <= maxIndex; index++ {
		result[index] = gss.stateMap[addIndexToKey(key, index)]
	}
	return result, nil
}

//GetInRangeKey get range value from [start,limit)
func (gss *GlobalStateStore) GetInRangeKey(contractName, start, limit string) []string {
	keyList := KeyList{}
	for key, _ := range gss.keyIndex {
		if key >= start && key < limit {
			keyList = append(keyList, key)
		}
	}
	sort.Sort(keyList)
	return keyList
}

//GetPrefixKey get key has prefix prefix
func (gss *GlobalStateStore) GetPrefixKey(prefix string) []string {
	keyList := KeyList{}
	for key, _ := range gss.keyIndex {
		if strings.HasPrefix(key, prefix) {
			keyList = append(keyList, key)
		}
	}
	sort.Sort(keyList)
	return keyList
}

//GetAll get all state data
func (gss *GlobalStateStore) GetAll() map[string]string {
	return gss.stateMap
}

func addIndexToKey(key string, index int) string {
	return key + Split + strconv.Itoa(index)
}

func parseKeyAndIndex(key string) (string, int, error) {
	lastIndex := strings.LastIndex(key, "#")
	if lastIndex == -1 {
		return "", -1, errors.New("[parseKeyAndIndex] not index in key: " + key)
	}
	index, err := strconv.Atoi(key[lastIndex+1:])
	if err != nil {
		return "", -1, err
	}
	return key[:lastIndex], index, nil
}
